﻿#pragma once
#include "OpenGL项目主标头.h"
#include "OpenGL着色器类标头.h"
// C/C++ 标准库学习网站：https://legacy.cplusplus.com
// C/C++ 参考学习网站：https://zh.cppreference.com/w/cpp
// C/C++ 微软(Visual Studio)中文学习网站：https://learn.microsoft.com/zh-cn/cpp/cpp/?view=msvc-170
// OpenGl 学习网站：https://www.khronos.org/opengl/wiki
// OpenGl 的 GLFW 学习网站：https://www.glfw.org/docs/latest/glfw3_8h.html#ac06b663d79c8fcf04669cc8fcc0b7670
// OpenGl 的 GLEW 学习网站：https://docs.gl/

/*
着色器基础
    着色器是一种高级编程语言，它可以用来定义图形渲染的各种操作，如顶点着色器、片元着色器、几何着色器等。
    着色器的主要作用是将图形渲染的计算任务交给硬件来执行，从而提高图形渲染的效率。
    着色器的编写语言有两种：GLSL（OpenGL Shading Language）和 HLSL（High Level Shader Language）。
    两种语言的主要区别在于语法和语义。GLSL 是 OpenGL 标准的基础语言，它是一种基于 C 的语言，语法简单，语义丰富。
    HLSL 是微软的专有语言，它是一种基于 C 的语言，语法复杂，语义精简。
    两种语言都可以用来编写顶点着色器和片元着色器，它们的主要区别在于它们的计算模型。
    顶点着色器的计算模型是逐顶点，它可以对每个顶点进行操作，如位置、颜色、法向量、纹理坐标等。
    片元着色器的计算模型是逐片元，它可以对每个片元进行操作，如颜色、深度、模板、屏幕坐标等。
    两种语言都可以用来编写几何着色器，它可以对顶点数据进行操作，如顶点变换、裁剪、投影等。
着色器的编写过程包括以下步骤：
    1. 编写源代码，并将其编译成可执行代码。
    2. 将可执行代码加载到 OpenGL 驱动中。
    3. 将可执行代码绑定到 OpenGL 状态机中。
    4. 调用 OpenGL API 函数，执行图形渲染操作。
    5. 调试和优化。

编写顶点着色器和片元着色器
    顶点着色器和片元着色器的源代码可以用文本编辑器编写，也可以使用专业的图形设计软件。
    顶点着色器的源代码文件名通常为“vs.glsl”或“vertex.glsl”，片元着色器的源代码文件名通常为“fs.glsl”或“fragment.glsl”。
    顶点着色器的源代码通常包含以下内容：
    layout (location = 0) in vec3 aPos; // 输入的顶点位置
    layout (location = 1) in vec3 aColor; // 输入的顶点颜色
    out vec3 ourColor; // 输出的颜色
    void main() {
        gl_Position = vec4(aPos, 1.0); // 设置顶点位置
        ourColor = aColor; // 设置顶点颜色
    }
片元着色器的源代码通常包含以下内容：
    in vec3 ourColor; // 输入的颜色
    out vec4 FragColor; // 输出的颜色
    void main() {
        FragColor = vec4(ourColor, 1.0); // 设置片元颜色
    }
    顶点着色器的输入变量 aPos 和 aColor 对应着顶点位置和颜色，输出变量 ourColor 对应着片元的颜色。
    片元着色器的输入变量 ourColor 对应着顶点着色器的输出变量 ourColor，输出变量 FragColor 对应着片元的颜色。
    顶点着色器的 main 函数设置顶点位置和颜色，片元着色器的 main 函数设置片元颜色。

编译和链接着色器
    编译和链接着色器的过程可以分为以下步骤：
        1. 创建着色器对象。
        2. 设置源代码。
        3. 编译着色器。
        4. 链接程序对象。
        5. 验证程序对象。
        6. 删除着色器对象。

编译着色器
    编译着色器的过程包括以下步骤：
        1. 创建着色器对象。
        2. 设置源代码。
        3. 编译着色器。
        4. 处理编译错误。
        5. 删除着色器对象。

链接程序对象
    链接程序对象的过程包括以下步骤：
        1. 创建程序对象。
        2. 附加着色器对象。
        3. 链接程序对象。
        4. 处理链接错误。
        5. 删除着色器对象。

验证程序对象
    验证程序对象的过程包括以下步骤：
        1. 创建程序对象。
        2. 附加着色器对象。
        3. 链接程序对象。
        4. 验证程序对象。
        5. 处理验证错误。
        6. 删除程序对象。

加载和绑定着色器
    加载和绑定着色器的过程包括以下步骤：
        1. 创建着色器对象。
        2. 编译着色器。
        3. 创建程序对象。
        4. 附加着色器对象。
        5. 链接程序对象。
        6. 验证程序对象。
        7. 使用程序对象。
        8. 删除着色器对象。
        9. 删除程序对象。

在 OpenGL 中色器代码的一些关键字：
    #version 460 core // 指定 GLSL 版本
    layout (location = 0) in vec3 aPos; // 输入的顶点位置
    layout (location = 1) in vec3 aColor; // 输入的顶点颜色
    out vec3 ourColor; // 输出的颜色
    void main() {
        gl_Position = vec4(aPos, 1.0); // 设置顶点位置
        ourColor = aColor; // 设置顶点颜色
    }
    in vec3 ourColor; // 输入的颜色
    out vec4 FragColor; // 输出的颜色
    void main() {
        FragColor = vec4(ourColor, 1.0); // 设置片元颜色
    }
    其中，#version 460 core 指定了 GLSL 版本为 4.60，core 表示标准核心，可以省略。
    layout (location = 0) in vec3 aPos; 声明了输入变量 aPos，其位置为 0。
    layout (location = 1) in vec3 aColor; 声明了输入变量 aColor，其位置为 1。
    out vec3 ourColor; 声明了输出变量 ourColor。
    gl_Position = vec4(aPos, 1.0); 设置顶点位置。
    ourColor = aColor; 设置顶点颜色。
    in vec3 ourColor; 声明了输入变量 ourColor。
    FragColor = vec4(ourColor, 1.0); 设置片元颜色。
    main() 函数设置片元颜色。
    顶点着色器和片元着色器的输入输出变量声明必须在顶部，在 main 函数之前。
    uniform 关键字用于声明全局变量，变量用于在运行时设置全局变量，如时间、摄像机位置等。
*/

// 着色器资源 ShaderSource 类的定义
ShaderSource::ShaderSource()	// 构造函数
    : vertex_shader_code(""), fragment_shader_code(""), geometry_shader_code(""),
    control_shader_code(""), evaluation_shader_code(""), compute_shader_code("") {
}

ShaderSource::ShaderSource(const ShaderSource& other) {
    vertex_shader_code = other.vertex_shader_code;
    fragment_shader_code = other.fragment_shader_code;
    geometry_shader_code = other.geometry_shader_code;
    control_shader_code = other.control_shader_code;
    evaluation_shader_code = other.evaluation_shader_code;
    compute_shader_code = other.compute_shader_code;
}

ShaderSource& ShaderSource::operator=(const ShaderSource& other) {
    vertex_shader_code = other.vertex_shader_code;
    fragment_shader_code = other.fragment_shader_code;
    geometry_shader_code = other.geometry_shader_code;
    control_shader_code = other.control_shader_code;
    evaluation_shader_code = other.evaluation_shader_code;
    compute_shader_code = other.compute_shader_code;
    return *this;
}

void ShaderSource::print() {
    /*std::cout << "Vertex Shader Code:\n" << vertex_shader_code << std::endl;
    std::cout << "Fragment Shader Code:\n" << fragment_shader_code << std::endl;
    std::cout << "Geometry Shader Code:\n" << geometry_shader_code << std::endl;
    std::cout << "Control Shader Code:\n" << control_shader_code << std::endl;
    std::cout << "Evaluation Shader Code:\n" << evaluation_shader_code << std::endl;
    std::cout << "Compute Shader Code:\n" << compute_shader_code << std::endl;*/
    printf("Vertex Shader Code:\n%s\n", vertex_shader_code.c_str());
    printf("Fragment Shader Code:\n%s\n", fragment_shader_code.c_str());
    printf("Geometry Shader Code:\n%s\n", geometry_shader_code.c_str());
    printf("Control Shader Code:\n%s\n", control_shader_code.c_str());
    printf("Evaluation Shader Code:\n%s\n", evaluation_shader_code.c_str());
    printf("Compute Shader Code:\n%s\n", compute_shader_code.c_str());
}

static bool my_strstr(const char* str, const char* substr) {
    // 字符串查找子串算法，不采用 KMP 算法，时间复杂度 O(n * m)
    // 注意 strstr 应该是至少或优于 O(n + m) 的算法，推荐使用
    uint32_t sub_len = strlen(substr);
    while (*str) { // 遍历字符串 str
        if (*str == *substr) // 找到匹配的字符
            for (size_t i = 1; i < sub_len; ++i) // 匹配子串
                if (str[i] != substr[i])// 子串不匹配
                    break;
                else if (str[i] == substr[i] && i == sub_len - 1) // 子串匹配成功
                    return true;
        ++str;
    }
    return false;
}


// 封装着色器类 Shader 类
Shader::Shader(const std::string& filename)
    : m_filename(filename) {
    m_source_code = parse_shader(filename.c_str()); // 解析着色器源代码
    m_shader_renderer_id = create_shader(m_source_code.vertex_shader_code, m_source_code.fragment_shader_code); // 创建着色器对象
    if (!m_shader_renderer_id)
        //cout << "Shader program creation failed!" << endl;
        printf("Shader program creation failed!\n");
}

Shader::~Shader() {
    printf("Shader program %ud deleted!\n", m_shader_renderer_id);
    gl_call(glDeleteProgram(m_shader_renderer_id));    // 删除着色器程序对象，释放资源
}

ShaderSource Shader::parse_shader(const char* filename) {
    // 分析载入链接文本的着色器代码
    enum ShaderType {
        UNKNOWN_SHADER = -1,    // 未知类型
        VERTEX_SHADER,          // 顶点着色器
        FRAGMENT_SHADER,        // 片元着色器
        GEOMETRY_SHADER,        // 几何着色器
        CONTROL_SHADER,         // 控制点着色器
        EVALUATION_SHADER,      // 评估着色器
        COMPUTE_SHADER          // 计算着色器
    };
    ShaderSource source_code = ShaderSource();   // 存放着色器源代码

    // 使用 C 风格的 IO 库
    FILE* file;
    fopen_s(&file, filename, "r"); // 打开文件，只读模式
    if (!file) {
        //cout << "Failed to open file: " << filename << endl;
        printf("Failed to open file: %s\n", filename);
        return source_code;
    }
    char line[4096];                    // 读取一行的缓冲区
    ShaderType type = UNKNOWN_SHADER;   // 着色器类型初始化为未知类型
    while (fgets(line, 4096, file) != nullptr) {  // 读取一行 (一行字符数需少于 4096)
        if (strstr(line, "_shader")) {
            if (my_strstr(line, "#vertex")) {          // 顶点着色器
                type = VERTEX_SHADER;
                continue;
            }     // 改变 type 类型，继续读取下一行
            else if (my_strstr(line, "#fragment")) {   // 片元着色器
                type = FRAGMENT_SHADER;
                continue;
            }
            else if (strstr(line, "#geometry")) {      // 几何着色器
                type = GEOMETRY_SHADER;
                continue;
            }
            else if (strstr(line, "#control")) {       // 控制点着色器
                type = CONTROL_SHADER;
                continue;
            }
            else if (strstr(line, "#evaluation")) {    // 评估着色器
                type = EVALUATION_SHADER;
                continue;
            }
            else if (strstr(line, "#compute")) {       // 计算着色器
                type = COMPUTE_SHADER;
                continue;
            }
        }
        else {
            if (type == VERTEX_SHADER)
                source_code.vertex_shader_code += line;       // 存放顶点着色器源代码
            else if (type == FRAGMENT_SHADER)
                source_code.fragment_shader_code += line;     // 存放片元着色器源代码
            else if (type == GEOMETRY_SHADER)
                source_code.geometry_shader_code += line;     // 存放几何着色器源代码
            else if (type == CONTROL_SHADER)
                source_code.control_shader_code += line;      // 存放控制点着色器源代码
            else if (type == EVALUATION_SHADER)
                source_code.evaluation_shader_code += line;   // 存放评估着色器源代码
            else if (type == COMPUTE_SHADER)
                source_code.compute_shader_code += line;      // 存放计算着色器源代码
        }
    }
    fclose(file); // 关闭文件

    // 使用 C++ 风格的处理文件库操作
    // 注意：C++ 风格的文件处理库比 C 风格的 IO 库更加方便，可以避免内存泄漏和缓冲区溢出等问题，但是性能不如 C 风格的文件读写
    /*std::ifstream file(filename);   // 打开文件，需要 <fstream> 头文件
    if (!file.is_open()) {  // 判断文件是否打开成功
        cout << "Failed to open file: " << filename << endl;
        return source_code;
    }
    std::string line;
    std::stringstream ss[6];// 字符串流，用于拼接字符串，这里初始化了 6 个 string，对应 6 种着色器类型
    ShaderType type = UNKNOWN_SHADER;   // 着色器类型初始化为未知类型
    while (std::getline(file, line)) {    // 读取一行，直到文件结束，需要 <string> 头文件
        if (line.find("shader") != std::string::npos) {    // 顶点着色器，std::string::npos 表示无效字符串
            if (line.find("#vertex") != std::string::npos)
                type = ShaderType::VERTEX_SHADER;
            else if (line.find("#fragment") != std::string::npos)
                type = ShaderType::FRAGMENT_SHADER;
            else if (line.find("#geometry") != std::string::npos)
                type = ShaderType::GEOMETRY_SHADER;
            else if (line.find("#control") != std::string::npos)
                type = ShaderType::CONTROL_SHADER;
            else if (line.find("#evaluation") != std::string::npos)
                type = ShaderType::EVALUATION_SHADER;
            else if (line.find("#compute") != std::string::npos)
                type = ShaderType::COMPUTE_SHADER;
        }
        else {
            ss[(int)type] << line << '\n';  // 存放对应着色器源代码，这就可以不用结构体了，但是还是返回结构体比较保险
            source_code.vertex_shader_code = ss[0].str();
            source_code.fragment_shader_code = ss[1].str();
            source_code.geometry_shader_code = ss[2].str();
            source_code.control_shader_code = ss[3].str();
            source_code.evaluation_shader_code = ss[4].str();
            source_code.compute_shader_code = ss[5].str();
        }
    }
    file.close(); */ // 关闭文件

    return source_code;
}

uint32_t Shader::compile_shader(uint32_t type, const std::string& source) {
    // 编译着色器
    GLuint vertex_shader_id = glCreateShader(type); // 创建着色器对象
    // 创建顶点着色器对象，GLuint = unsigned int
    const char* src = source.c_str(); // 获取源代码的指针，相当于 &source[0]
    glShaderSource(vertex_shader_id, 1, &src, nullptr); // 设置源代码，第一个参数表示指定了有几个源代码；第二个参数指定 string（第三个参数） 和 length（第四个参数） 数组中的元素数；第三个参数是源代码的指针；第四个参数是长度，这里设置为 nullptr 表示长度为 0
    glCompileShader(vertex_shader_id); // 编译着色器，编译成功后会生成可执行代码

    // 着色器错误处理：
    int success_flag;
    glGetShaderiv(vertex_shader_id, GL_COMPILE_STATUS, &success_flag); // 获取编译状态
    if (!success_flag/* == GL_FALSE*/) {  // 鼠标放在变量上会显示它的类型和值，可以选中后使用 alt + f12 快捷键快速查看
        //char error_message[512];
        //glGetShaderInfoLog(vertex_shader_id, 512, nullptr, error_message); // 获取错误信息
        // 另一种实现（堆区）方式：
        int length;
        glGetShaderiv(vertex_shader_id, GL_INFO_LOG_LENGTH, &length);
        char* error_message = (char*)malloc(length * sizeof(char));
        if (error_message == nullptr) {
            //cout << "Shader compilation failed and Memory allocation failed!" << endl;
            printf("Shader compilation failed and Memory allocation failed!");
            return 0;
        }
        glGetShaderInfoLog(vertex_shader_id, length, &length, error_message);
        /*cout << "Shader compilation failed: " << (type == GL_VERTEX_SHADER ? "Vertex" : "Fragment") << endl;
        cout << "error message: " << error_message << endl; */
        printf("Shader compilation failed: %s\n", (type == GL_VERTEX_SHADER ? "Vertex" : "Fragment"));
        printf("error message: %s\n", error_message);
        free(error_message); // 释放堆区内存
        glDeleteShader(vertex_shader_id); // 删除着色器对象
        return 0;
    }
    return vertex_shader_id;
}

uint32_t Shader::create_shader(std::string& vertex_shader, std::string& fragment_shader) {
    // 创建着色器程序对象
    uint32_t program = glCreateProgram(); // 创建程序对象
    uint32_t vertex_shader_id = compile_shader(GL_VERTEX_SHADER, vertex_shader); // 创建顶点着色器对象
    uint32_t fragment_shader_id = compile_shader(GL_FRAGMENT_SHADER, fragment_shader); // 创建片元着色器对象
    glAttachShader(program, vertex_shader_id); // 将顶点着色器对象附加到程序对象
    glAttachShader(program, fragment_shader_id); // 将片元着色器对象附加到程序对象
    glLinkProgram(program); // 链接程序对象
    glValidateProgram(program); // 验证程序对象

    // 程序错误处理：
    int success_flag;
    glGetProgramiv(program, GL_LINK_STATUS, &success_flag); // 获取链接状态
    if (!success_flag) {
        char info_log[4096];
        glGetProgramInfoLog(program, 4096, nullptr, info_log); // 获取错误信息
        cout << "Program linking failed: " << info_log << endl;
        printf("Program linking failed: %s\n", info_log);
        //glDeleteProgram(program); // 删除程序对象
        //glDeleteShader(vertex_shader_id);   // 删除顶点着色器对象
        return 0;
    }

    glDeleteShader(vertex_shader_id);   // 删除顶点着色器对象
    glDeleteShader(fragment_shader_id); // 删除片元着色器对象
    return program;
}

int Shader::get_uniform_location(const std::string& name) const {
    if (m_uniform_location_map.find(name) != m_uniform_location_map.end()) // 查询缓存，find() 函数返回一个迭代器，如果找到了，返回迭代器所指的位置，否则返回 end() 迭代器
        return m_uniform_location_map.at(name); // 如果之前已经查询过，直接返回结果

    gl_call(int get_location = glGetUniformLocation(m_shader_renderer_id, name.c_str())); // 获取着色器程序中纹理的位置（get_location 是一个统一变量，在着色器程序中可以用它来获取 uniform 变量的位置）
    //gl_assert(get_location != -1);  // 检查着色器程序中是否存在 uniform 变量 name
    if (get_location == -1)
        printf("Uniform variable '%s' not found in shader program main function!\n", name.c_str());

    m_uniform_location_map[name] = get_location; // 缓存查询结果
    return get_location;
}

void Shader::use_program() const {
    gl_call(glUseProgram(m_shader_renderer_id)); // 使用着色器程序（绑定着色器程序）
}

void Shader::not_use_program() const {
    gl_call(glUseProgram(0)); // 解除绑定着色器程序（解除使用）
}

void Shader::set_uniform4f(const std::string& name, float red, float green, float blue, float alpha) const {
    //gl_call(glUniform4f(glGetUniformLocation(m_shader_renderer_id, name.c_str()), red, green, blue, alpha)); // 设置初始颜色，不用储存 m_uniform_location_map 成员，但每次调用都需要 glGetUniformLocation 函数，速度较慢
    gl_call(glUniform4f(get_uniform_location(name), red, green, blue, alpha)); // 设置初始颜色，学习哈希表（字典）版本
}

void Shader::set_uniform1f(const std::string& name, float v1) const {
    //gl_call(glUniform4f(glGetUniformLocation(m_shader_renderer_id, name.c_str()), red, green, blue, alpha)); // 设置初始颜色，不用储存 m_uniform_location_map 成员，但每次调用都需要 glGetUniformLocation 函数，速度较慢
    gl_call(glUniform1f(get_uniform_location(name), v1)); // 设置初始颜色，学习哈希表（字典）版本
}

void Shader::set_uniform4f(int& location, float red, float green, float blue, float alpha) const {
    gl_call(glUniform4f(location, red, green, blue, alpha)); // 设置初始颜色
}

void Shader::set_uniform1f(int& location, float v1) const {
    gl_call(glUniform1f(location, v1)); // 设置初始颜色
}

void Shader::set_uniform1i(const std::string& name, int v1) const {
    gl_call(glUniform1i(get_uniform_location(name), v1)); // 设置初始颜色，学习哈希表（字典）版本，用于绑定纹理
}

void Shader::set_uniform1i(int& location, int v1) const {
    gl_call(glUniform1i(location, v1)); // 设置初始颜色，用于绑定纹理
}

void Shader::set_uniform_matrix4fv(int& location, const glm::mat4& matrix) const {
    gl_call(glUniformMatrix4fv(location, 1, GL_FALSE, &matrix[0][0]));
}

void Shader::set_uniform_matrix4fv(const std::string& name, const glm::mat4& matrix) const {
    gl_call(glUniformMatrix4fv(get_uniform_location(name), 1, GL_FALSE, &matrix[0][0]));
}

ShaderSource& Shader::get_source_code() {
    return m_source_code;
}
